/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.geode.internal.statistics;

import org.apache.geode.*;
import org.apache.geode.internal.i18n.LocalizedStrings;

/**
 * Describes an individual statistic whose value is updated by an
 * application and may be archived by GemFire.  These descriptions are
 * gathered together in a {@link StatisticsType}.
 *
 * @see Statistics
 *
 *
 * @since GemFire 3.0
 */
public final class StatisticDescriptorImpl implements StatisticDescriptor {

  /** A constant for an <code>byte</code> type */
  static final byte BYTE = (byte) 3;

  /** A constant for an <code>short</code> type */
  static final byte SHORT = (byte) 4;

  /** A constant for an <code>int</code> type */
  static final byte INT = (byte) 5;

  /** A constant for an <code>long</code> type */
  static final byte LONG = (byte) 6;

  /** A constant for an <code>float</code> type */
  static final byte FLOAT = (byte) 7;

  /** A constant for an <code>double</code> type */
  static final byte DOUBLE = (byte) 8;

  ////////////////////  Instance Fields  ////////////////////

  /** An unitialized offset */
  private int INVALID_OFFSET = -1;

  /** The name of the statistic */
  private final String name;

  /** The type code of this statistic */
  private final byte typeCode;

  /** A description of the statistic */
  private final String description;

  /** The unit of the statistic */
  private final String unit;

  /** Is the statistic a counter? */
  private final boolean isCounter;

  /** Do larger values of the statistic indicate better performance? */
  private final boolean isLargerBetter;

  /** The physical offset used to access the data that stores the
   * value for this statistic in an instance of {@link Statistics}
   */
  private int id = INVALID_OFFSET;

  //////////////////////  Static Methods  //////////////////////

  /**
   * Returns the name of the given type code
   *
   * @throws IllegalArgumentException
   *         <code>code</code> is an unknown type
   */
  public final static String getTypeCodeName(int code) {
    switch (code) {
    case BYTE: 
      return "byte";
    case SHORT: 
      return "short";
    case FLOAT: 
      return "float";
    case INT: 
      return "int";
    case LONG:
      return "long";
    case DOUBLE:
      return "double";
    default:
      throw new IllegalArgumentException(LocalizedStrings.StatisticDescriptorImpl_UNKNOWN_TYPE_CODE_0.toLocalizedString(Integer.valueOf(code)));
    }
  }

  /**
   * Returns the number of bits needed to represent a value of the given type
   *
   * @throws IllegalArgumentException
   *         <code>code</code> is an unknown type
   */
  public final static int getTypeCodeBits(int code) {
    switch (code) {
    case BYTE: 
      return 8;
    case SHORT: 
      return 16;
    case FLOAT: 
      return 32;
    case INT: 
      return 32;
    case LONG:
      return 64;
    case DOUBLE:
      return 64;
    default:
      throw new IllegalArgumentException(LocalizedStrings.StatisticDescriptorImpl_UNKNOWN_TYPE_CODE_0.toLocalizedString(Integer.valueOf(code)));
    }
  }

  /**
   * Returns the class of the given type code
   *
   * @throws IllegalArgumentException
   *         <code>code</code> is an unknown type
   */
  public final static Class<?> getTypeCodeClass(byte code) {
    switch (code) {
    case BYTE: 
      return byte.class;
    case SHORT: 
      return short.class;
    case FLOAT: 
      return float.class;
    case INT: 
      return int.class;
    case LONG:
      return long.class;
    case DOUBLE:
      return double.class;
    default:
      throw new IllegalArgumentException(LocalizedStrings.StatisticDescriptorImpl_UNKNOWN_TYPE_CODE_0.toLocalizedString(Integer.valueOf(code)));
    }
  }

  public static StatisticDescriptor createIntCounter(String name, String description,
                                              String units, boolean isLargerBetter) {
    return new StatisticDescriptorImpl(name, INT, description, units, true, isLargerBetter);
  }
  public static StatisticDescriptor createLongCounter(String name, String description,
                                               String units, boolean isLargerBetter) {
    return new StatisticDescriptorImpl(name, LONG, description, units, true, isLargerBetter);
  }
  public static StatisticDescriptor createDoubleCounter(String name, String description,
                                                 String units, boolean isLargerBetter) {
    return new StatisticDescriptorImpl(name, DOUBLE, description, units, true, isLargerBetter);
  }
  public static StatisticDescriptor createIntGauge(String name, String description,
                                            String units, boolean isLargerBetter) {
    return new StatisticDescriptorImpl(name, INT, description, units, false, isLargerBetter);
  }
  public static StatisticDescriptor createLongGauge(String name, String description,
                                             String units, boolean isLargerBetter) {
    return new StatisticDescriptorImpl(name, LONG, description, units, false, isLargerBetter);
  }
  public static StatisticDescriptor createDoubleGauge(String name, String description,
                                               String units, boolean isLargerBetter) {
    return new StatisticDescriptorImpl(name, DOUBLE, description, units, false, isLargerBetter);
  }

  ///////////////////////  Constructors  ///////////////////////

  /**
   * Creates a new description of a statistic.
   *
   * @param name
   *        The name of the statistic (for example,
   *        <code>"numDatabaseLookups"</code>) 
   * @param typeCode
   *        The type of the statistic.  This must be either
   *        <code>int.class</code>, <code>long.class</code>, or
   *        <code>double.class</code>.
   * @param description
   *        A description of the statistic (for example, <code>"The
   *        number of database lookups"</code>
   * @param unit
   *        The units that this statistic is measure in (for example,
   *        <code>"milliseconds"</code>) 
   * @param isCounter
   *        Is this statistic a counter?  That is, does its value
   *        change monotonically (always increases or always
   *        decreases)? 
   * @param isLargerBetter
   *        True if larger values indicate better performance.
   *
   * @throws IllegalArgumentException
   *         <code>type</code> is not one of <code>int.class</code>,
   *         <code>long.class</code>, or <code>double.class</code>.
   */
  private StatisticDescriptorImpl(String name, byte typeCode,
                                  String description, String unit,
                                  boolean isCounter,
                                  boolean isLargerBetter) {
    this.name = name;
    this.typeCode = typeCode;
    if (description == null) {
      this.description = "";

    } else {
      this.description = description;
    }

    if (unit == null) {
      this.unit = "";

    } else {
      this.unit = unit;
    }
    this.isCounter = isCounter;
    this.isLargerBetter = isLargerBetter;
  }

  ////////////////////  StatisticDescriptor Methods  ////////////////////

  public final String getName() {
    return this.name;
  }

  public final String getDescription() {
    return this.description;
  }

  public final Class<?> getType() {
    return getTypeCodeClass(this.typeCode);
  }

  public final int getStorageBits() {
    return getTypeCodeBits(this.typeCode);
  }

  public final boolean isCounter() {
    return this.isCounter;
  }
  
  public final boolean isLargerBetter() {
    return this.isLargerBetter;
  }
  
  public final String getUnit() {
    return this.unit;
  }

  public final int getId() {
//     if (this.id == INVALID_OFFSET) {
//       String s = "The id has not been initialized yet.";
//       throw new IllegalStateException(s);
//     }

    //Assert.assertTrue(this.id >= 0);
    return this.id;
  }
  
  public final Number getNumberForRawBits(long bits) {
    switch (this.typeCode) {
    case StatisticDescriptorImpl.INT:
      return (int)bits;
    case StatisticDescriptorImpl.LONG:
      return bits;
    case StatisticDescriptorImpl.DOUBLE:
      return Double.longBitsToDouble(bits);
    default:
      throw new RuntimeException(LocalizedStrings.StatisticsImpl_UNEXPECTED_STAT_DESCRIPTOR_TYPE_CODE_0.toLocalizedString(Byte.valueOf(this.typeCode)));
    }
  }
  
  ////////////////////  Instance Methods  ////////////////////

  /**
   * Returns the type code of this statistic
   */
  public final byte getTypeCode() {
    return this.typeCode;
  }

  /**
   * Sets the id of this descriptor
   */
  final void setId(int id) {
    //Assert.assertTrue(id >= 0);
    this.id = id;
  }

  ////////////////////  Comparable Methods  ////////////////////
  /**
   * <code>StatisticDescriptor</code>s are naturally ordered by their
   * name. 
   *
   * @throws IllegalArgumentException
   *         <code>o</code> is not a <code>StatisticDescriptor</code>
   *
   * @see #getName
   */
  public int compareTo(StatisticDescriptor o) {
    return this.getName().compareTo(o.getName());
  }

  public final int checkInt() {
    if (this.typeCode != INT) {
      throw new IllegalArgumentException(LocalizedStrings.StatisticDescriptorImpl_THE_STATISTIC_0_WITH_ID_1_IS_OF_TYPE_2_AND_IT_WAS_EXPECTED_TO_BE_AN_INT.toLocalizedString(new Object[] {getName(), Integer.valueOf(getId()), StatisticDescriptorImpl.getTypeCodeName(getTypeCode())}));
    }
    return this.id;
  }

  public final int checkLong() {
    if (this.typeCode != LONG) {
      StringBuffer sb = new StringBuffer();
      sb.append("The statistic " + getName() + " with id ");
      sb.append(getId());
      sb.append(" is of type ");
      sb.append(StatisticDescriptorImpl.getTypeCodeName(getTypeCode()));
      
      sb.append(" and it was expected to be a long");
      throw new IllegalArgumentException(sb.toString());
    }
    return this.id;
  }

  public final int checkDouble() {
    if (this.typeCode != DOUBLE) {
      throw new IllegalArgumentException(LocalizedStrings.StatisticDescriptorImpl_THE_STATISTIC_0_WITH_ID_1_IS_OF_TYPE_2_AND_IT_WAS_EXPECTED_TO_BE_A_DOUBLE.toLocalizedString(new Object[] {getName(), Integer.valueOf(getId()), StatisticDescriptorImpl.getTypeCodeName(getTypeCode())}));
    }
    return this.id;
  }


  @Override // GemStoneAddition
  public int hashCode() {
    return getName().hashCode();
  }
  @Override
  public boolean equals(Object o) {
    if (o == null) {
      return false;
    }
    if (!(o instanceof StatisticDescriptorImpl)) {
      return false;
    }
    StatisticDescriptorImpl other = (StatisticDescriptorImpl)o;
    if (getId() != other.getId()) {
      return false;
    }
    if (!getName().equals(other.getName())) {
      return false;
    }
    if (isCounter() != other.isCounter()) {
      return false;
    }
    if (isLargerBetter() != other.isLargerBetter()) {
      return false;
    }
    if (!getType().equals(other.getType())) {
      return false;
    }
    if (!getUnit().equals(other.getUnit())) {
      return false;
    }
    if (!getDescription().equals(other.getDescription())) {
      return false;
    }
    return true;
  }
}
